package com.cn.file.index;

import com.cn.exception.MyException;
import com.cn.file.MappFile;
import org.omg.CORBA.DynAnyPackage.Invalid;

import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.List;

public class IndexFile {

    private IndexHeader indexHeader;
    private FileChannel fileChannel;
    private FileLock fileLock;
    private MappedByteBuffer mappedByteBuffer;
    private MappFile mappFile;

    //hash槽的总数
    private final int hashSlotNum;
    //索引总数
    private final int indexCount;

    //index文件头大小
    private final int HEAD_SIZE = 40;
    //index文件index部分每条消息大小
    private final int INDEX_SIZE = 20;
    //hash大小
    private final int HASHSLOT_SIZE = 4;
    private final int invalidIndex = 0;

    public IndexFile(String path, int hashSlotNum, int indexCount) {
        this.hashSlotNum = hashSlotNum;
        this.indexCount = indexCount;
        //文件总大小
        long fileSize = HEAD_SIZE + (INDEX_SIZE * indexCount) + (hashSlotNum * HASHSLOT_SIZE);
        this.mappFile = new MappFile(path, fileSize);
        this.fileChannel = this.mappFile.getFileChannel();
        this.mappedByteBuffer = this.mappFile.getMappedByteBuffer();
        this.indexHeader = new IndexHeader(this.mappedByteBuffer.slice());
    }

    public int putKey(String key, long phyoffset) {
        if (this.mappedByteBuffer == null) {
            throw new MyException("index文件不能为空！");
        }
        if (this.indexHeader.getIndexCount() < indexCount) {
            //计算key的hash值
            int hashKey = buildKeyHash(key);
            //该key应该落在那个hash槽的位置
            int absSlot = hashKey % hashSlotNum;
            //实际存放位置
            int absSlotIndex = HEAD_SIZE + absSlot * HASHSLOT_SIZE;

            //查询是否存在冲突的hash key（在该位置是否已经存在相同hash的key）
            int preIndexcount = this.mappedByteBuffer.getInt(absSlotIndex);
            if (preIndexcount < invalidIndex) {
                preIndexcount = invalidIndex;
            }

            //存放index（40+5*4+ 1*20=80 ）
            int absHashIndex = HEAD_SIZE + hashSlotNum * HASHSLOT_SIZE + (INDEX_SIZE * this.indexHeader.getIndexCount());
            this.mappedByteBuffer.putInt(absHashIndex, hashKey);//hash存放的物理位置，消息检索的时候用到，一个槽位上存在很多个消息，从众多消息里面检索出hash一样的消息
            this.mappedByteBuffer.putLong(absHashIndex + 4, phyoffset);//存放消息在commitlog中的真实位移

            long now = System.currentTimeMillis();
            long timediff = now - this.indexHeader.getBegintStamp();
            timediff = timediff / 1000;
            if (timediff < 0) {
                timediff = 0;
            } else if (timediff > Integer.MAX_VALUE) {
                timediff = Integer.MAX_VALUE;
            }
            //时间差（存储时间差的理由：减少存储空间，8字节的存储空间 变为 4字节的存储空间）
            this.mappedByteBuffer.putInt(absHashIndex + 4 + 8, (int) timediff);
            //如果存在key的冲突，假设key1 与 key2的hash值一样，key1先存进来，那么存key2的时候，此处就存放key1消息是整个索引中的第几条消息
            this.mappedByteBuffer.putInt(absHashIndex + 4 + 8 + 4, preIndexcount);

            //这是第几个index消息【在hash槽的位置，存放当前消息是索引中的第几条（通过该值，可以快速定位的消息）】
            this.mappedByteBuffer.putInt(absSlotIndex, this.indexHeader.getIndexCount());

            if (this.indexHeader.getBegintStamp() <= 0) {
                this.indexHeader.resetBeginTimestamp(now);
                this.indexHeader.resetBeginPhyoffset(phyoffset);
            }

            //保存最新一条index的存储时间
            this.indexHeader.resetEndTimestamp(now);
            //保存最新一条index的物理位置
            this.indexHeader.resetEndPhyoffset(phyoffset);
            //hash总数+1
            this.indexHeader.incrHashSlotnum();
            //消息总数+1
            this.indexHeader.incrIndexCount();

            //头部
            this.indexHeader.flush();

            return 1;
        } else {
            return 0;
        }
    }

    public List<Long> selectPhyOffset(List<Long> phyoffset, String key) {
        if (this.mappedByteBuffer == null) {
            throw new MyException("index索引文件不存在");
        }
        if (phyoffset == null) {
            phyoffset = new ArrayList<>();
        }

        //key的hash值
        int hashKey = buildKeyHash(key);
        //计算hash的散落位置
        int absSlot = hashKey % hashSlotNum;
        //计算hash在物理文件中的实际位置
        int absSlotIndex = HEAD_SIZE + absSlot * HASHSLOT_SIZE;

        //多个hash可能存储在同一个物理位置上，那么这里查询出旧值
        int indexCount = this.mappedByteBuffer.getInt(absSlotIndex);

        for (int nextCount = indexCount; ; ) {
            //计算index索引在文件中存放位置
            int absHashIndex = HEAD_SIZE + hashSlotNum * HASHSLOT_SIZE + (INDEX_SIZE * nextCount);
            //查询hash值
            int absHashKey = this.mappedByteBuffer.getInt(absHashIndex);
            //查询消息的存储位置
            long absPyhoffset = this.mappedByteBuffer.getLong(absHashIndex + 4);
            if (absHashKey == hashKey) {
                phyoffset.add(absPyhoffset);
                break;
            }
            //查询旧的消息
            int preIndexCount = this.mappedByteBuffer.getInt(absHashIndex + 4 + 8 + 4);
            if (preIndexCount <= 0) {
                break;
            }
            nextCount = preIndexCount;
        }
        return phyoffset;
    }

    public void flush() {
        //文件头刷盘
        this.indexHeader.flush();
        //文件体刷盘
        this.mappedByteBuffer.force();
    }


    /**
     * 计算hash
     *
     * @param key
     * @return
     */
    public int buildKeyHash(String key) {
        int hash = key.hashCode();
        hash = Math.abs(hash);
        if (hash < 0) {
            hash = invalidIndex;
        }
        return hash;
    }

    public IndexHeader getIndexHeader() {
        return indexHeader;
    }

    public static void main(String[] args) {
        String path = "F:/Temp/123";
        int hashSlotnum = 5;
        int indexCount = 100;
        IndexFile indexFile = new IndexFile(path, hashSlotnum, indexCount);

//        String[] strs = new String[]{"0", "5"};
//        for (int i = 0; i < 50; i++) {
//            indexFile.putKey("" + i, i);
//        }
//        indexFile.flush();

        List<Long> list = new ArrayList<>();
        indexFile.selectPhyOffset(list, "12");
        System.out.println(list);
    }

}
